#include "SPCamera.h"

namespace SP
{
	//--------------------------------------------------------------------------------------
	// SPCamera
	//-------------------------------------------------------------------------------------
	SPCamera::SPCamera()
	{
		m_vEyePt = XMFLOAT3( 0.0f, 0.0f, 6.0f );
		m_vLookatPt = XMFLOAT3( 0.0f, 0.0f, -0.0f );	
	}

	void SPCamera::SetWindow( INT width, INT height )
	{
		m_nWidth = width;
		m_nHeight = height;
		XMMATRIX mProjXM = XMMatrixPerspectiveFovLH( XM_PIDIV4, m_nWidth / ( FLOAT )m_nHeight, 0.01f, 10000.0f );
		XMStoreFloat4x4( &m_mProj, mProjXM );

		XMMATRIX mOrtho = XMMatrixOrthographicLH( (float)width, (float)height, 0.01f, 10.0f );
		XMStoreFloat4x4( &m_mOrthoMatrix, mOrtho );
	}

	void SPCamera::SetCamera(const XMFLOAT3& vEye, const XMFLOAT3& vLookAt, const XMFLOAT3& vUp /* = XMFLOAT3 */)
	{
		m_vEyePt = vEye;
		m_vLookatPt = vLookAt;

		XMVECTOR Eye = XMLoadFloat3( &vEye );
		XMVECTOR At = XMLoadFloat3( &vLookAt );
		XMVECTOR Up = XMLoadFloat3( &vUp );
		XMMATRIX mView = XMMatrixLookAtLH( Eye, At, Up );
		XMMATRIX mViewInv = XMMatrixInverse( &Up, mView );
		XMStoreFloat4x4( &m_mView, mView );
		XMStoreFloat4x4( &m_mViewInv, mViewInv );
	}

	//--------------------------------------------------------------------------------------
	// SPModelViewerCamera
	//-------------------------------------------------------------------------------------
	SPModelViewerCamera::SPModelViewerCamera()
	{
		XMStoreFloat4( &m_qNow, XMQuaternionIdentity( ) );
		XMStoreFloat4( &m_qDown, XMQuaternionIdentity( ) );
		XMStoreFloat4x4( &m_mRotation, XMMatrixIdentity( ) );

		m_bDrag = FALSE;
		
		m_fRadius = 1.215f;

		m_vDownPt = XMFLOAT3( 0.0f, 0.0f, 1.0f );
		m_vCurrentPt = XMFLOAT3( 0.0f, 0.0f, 1.0f );
		m_fEyeRadius = 6.0;
		OnBegin( 0 ,0 );
		OnMove( 1, 0 );
		OnEnd();
	}

	XMFLOAT3 SPModelViewerCamera::ScreenToVector( float fScreenPtX, float fScreenPtY )
	{
		// Scale to screen
		FLOAT x = - ( fScreenPtX - m_nWidth / 2) / ( m_fRadius * m_nWidth / 2 );
		FLOAT y = - ( fScreenPtY - m_nHeight / 2) / ( m_fRadius * m_nHeight / 2 );

		FLOAT z = 0.0f;
		FLOAT mag = x * x + y * y;

		if ( mag > 1.0f )
		{
			FLOAT scale = 1.0f / sqrtf( mag );
			x *= scale;
			y *= scale;
		}
		else
			z = sqrtf( 1.0f - mag );

		// Return vector
		return XMFLOAT3( x, y, z );
	}

	XMVECTOR SPModelViewerCamera::QuatFromBallPoints( const XMVECTOR vFrom, const XMVECTOR vTo )
	{	
		if ( XMVector3Equal( vFrom, vTo ) )
			return  XMQuaternionIdentity( );
		float w = XMVectorGetX( XMVector3AngleBetweenNormals( vFrom, vTo ) );
		XMVECTOR vPart = XMVector3Cross( vFrom, vTo );
		return XMQuaternionRotationAxis( vPart, w * 2.0f );
	}

	void SPModelViewerCamera::OnBegin( int nX, int nY )
	{
		// Only enter the drag state if the click falls
		// inside the click rectangle.
		if( nX >= 0 &&
			nX < ( int )( m_nWidth ) &&
			nY >= 0 &&
			nY <  ( int )( m_nHeight ) )
		{
			m_bDrag = true;
			m_qDown = m_qNow;
			m_vDownPt = ScreenToVector( ( float )nX, ( float )nY );
		}
	}

	void SPModelViewerCamera::OnMove( int nX, int nY )
	{
		if ( m_bDrag )
		{
			m_vCurrentPt = ScreenToVector( ( float )nX, ( float )nY );

			XMVECTOR qDown = XMLoadFloat4( &m_qDown );
			XMVECTOR vDownPt = XMLoadFloat3( &m_vDownPt );
			XMVECTOR vCurrentPt = XMLoadFloat3( &m_vCurrentPt );
			qDown = XMQuaternionMultiply( qDown, QuatFromBallPoints( vDownPt, vCurrentPt ) );
			XMStoreFloat4( &m_qNow, qDown );
			// Simply update
			Update();
		}
	}

	void SPModelViewerCamera::OnEnd()
	{
		m_bDrag = false;
	}

	void SPModelViewerCamera::Update()
	{
		XMVECTOR vDeterminant;
		XMMATRIX mCameraRot = GetRotationMatrix();

		mCameraRot = XMMatrixInverse( &vDeterminant, mCameraRot );

		XMVECTOR Eye = XMVectorSet( 0.0f, 0.0f, m_fEyeRadius, 0.0f );
		XMVECTOR At = XMVectorSet( 0.0f, 0.0f, 0.0f, 0.0f );
		XMVECTOR Up = XMVectorSet( 0.0f, 1000.0f, 0.0f, 0.0f );
		Eye = XMVector3Transform( Eye, mCameraRot );
		Up = XMVector3Transform( Up, mCameraRot );

		XMMATRIX mView = XMMatrixLookAtLH( Eye, At, Up );
		XMStoreFloat4x4( &m_mView, mView );

		// Store EyePos
		XMStoreFloat3( &m_vEyePt, Eye );

		// mtxViewInv
		XMMATRIX mViewInv = XMMatrixInverse( &vDeterminant, mView );
		XMStoreFloat4x4( &m_mViewInv, mViewInv ); 
	}

	void SPModelViewerCamera::Closer()
	{
		m_fEyeRadius *= 1.1f;
		Update();
	}

	void SPModelViewerCamera::Further()
	{
		m_fEyeRadius *= 0.90909f;
		Update();
	}
}